package framework.crypto;

import framework.config.GeneralCryptoConfig;
import framework.exceptions.BusinessException;
import lombok.Getter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;

import javax.crypto.Cipher;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.math.BigInteger;
import java.net.URL;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.util.Base64;

/**
 * 一般性加密算法实现
 */
@Slf4j
public class GeneralCryptoImpl implements GeneralCrypto {

    @Getter
    private GeneralCryptoConfig config;
    @Getter
    private PrivateKey privateKey = null;
    @Getter
    private PublicKey publicKey = null;
    @Getter
    private String publicKeyContent = null;

    public GeneralCryptoImpl(GeneralCryptoConfig config) {
        this.config = config;
        this.loadKey();
    }

    private InputStream loadPrivateKey() throws IOException {
        String privateKeyPath = this.config.getPrivateKeyPath();
        if (!StringUtils.hasText(privateKeyPath))
            throw new BusinessException("Not config sys.crypto.general.private-key-path");
        //
        InputStream inputStream;
        if (privateKeyPath.startsWith("file://"))
            inputStream = new URL(privateKeyPath).openStream();
        else
            inputStream = GeneralCryptoConfig.class.getClassLoader().getResourceAsStream(privateKeyPath);
        //
        if (inputStream == null)
            throw new BusinessException("Not config sys.crypto.general.private-key-path");
        //
        return inputStream;
    }

    @SneakyThrows
    private void loadKey() {
        try (InputStream inputStream = this.loadPrivateKey()) {
            // 移除私钥文件中的头部和尾部信息，只保留PEM格式的Base64编码内容
            StringBuffer buffer = new StringBuffer();
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"))) {
                String line = null;
                while ((line = reader.readLine()) != null) {
                    if (line.length() == 0) continue;
                    if (line.startsWith("-----")) continue;
                    buffer.append(line);
                }
            }

            // 将私钥文件内容转换为字符串
            String privateKeyContent = buffer.toString();

            // 解码Base64编码的私钥内容
            byte[] privateKeyBytes = Base64.getDecoder().decode(privateKeyContent);

            // 根据PKCS#8规范生成PrivateKey对象
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            this.privateKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(privateKeyBytes));

            //
            RSAPrivateKeySpec priv = keyFactory.getKeySpec(privateKey, RSAPrivateKeySpec.class);
            RSAPublicKeySpec keySpec = new RSAPublicKeySpec(priv.getModulus(), BigInteger.valueOf(65537));
            this.publicKey = keyFactory.generatePublic(keySpec);
            byte[] publicKeyBytes = this.publicKey.getEncoded();
            Base64.Encoder mimeEncoder = Base64.getMimeEncoder(64, "\r\n".getBytes());
            this.publicKeyContent = "-----BEGIN PUBLIC KEY-----\r\n"
                    + mimeEncoder.encodeToString(publicKeyBytes)
                    + "\r\n-----END PUBLIC KEY-----";
        }
    }

    @SneakyThrows
    public byte[] encrypt(byte[] data) {
        Cipher cipher = this.getCipher();
        cipher.init(Cipher.ENCRYPT_MODE, this.publicKey);
        byte[] encryptedBytes = cipher.doFinal(data);
        return encryptedBytes;
    }

    @SneakyThrows
    public byte[] decrypt(byte[] data) {
        Cipher cipher = this.getCipher();
        cipher.init(Cipher.DECRYPT_MODE, this.privateKey);
        byte[] decryptedBytes = cipher.doFinal(data);
        return decryptedBytes;
    }

    @SneakyThrows
    private Cipher getCipher() {
        Cipher cipher = null;
        if (config.getProvider() == null)
            cipher = Cipher.getInstance(config.getCipher());
        else
            cipher = Cipher.getInstance(config.getCipher(), config.getProvider());
        return cipher;
    }

}
